package dnl.util.lang.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * General reflection utility methods that are concerned with
 * <code>Methods</code>s.
 * 
 * @author <a href="mailto:daniel@nlayers.com">Daniel Or</a>
 */
public class Methods {

	public enum AccessorType {GETTER, SETTER};
	
	/**
	 * Gets all the setter methods available in class c (including methods
	 * defined in super types).
	 * 
	 * @param c
	 *            a <code>Class</code>.
	 * @return the setter methods defined in class <code>c</code>.
	 */
	public static List<Method> getSetters(Class<?> c) {
		return getMethodsWithPrefix(c, "set");
	}

	/**
	 * Gets all the getter methods available in class c (including methods
	 * defined in super types).
	 * 
	 * @param c
	 *            a <code>Class</code>.
	 * @return the getter methods defined in class <code>c</code>.
	 */
	public static List<Method> getGetters(Class<?> c) {
		return getMethodsWithPrefix(c, "get");
	}

	/**
	 * Gets a getter method for the given property on the given class. If the
	 * property is of a boolean type, the method will return accessors that has
	 * a 'has' or 'is' prefix.
	 * 
	 * @param c
	 *            a <code>Class</code>.
	 * @param propertyName
	 *            name of a property in the given class.
	 * @return the corresponding getter method or <code>null</code> if a
	 *         getter does not exist or in any case of access problem.
	 */
	public static Method getGetter(Class<?> c, String propertyName){
		Method getterMethod = getPrefixedMethod(c, propertyName, AccessorType.GETTER);
		return getterMethod;
	}

	/**
	 * Gets a getter method for the given property on the given class. If the
	 * property is of a boolean type, the method will return accessors that has
	 * a 'has' or 'is' prefix.
	 * 
	 * @param c
	 *            a <code>Class</code>.
	 * @param propertyName
	 *            name of a property in the given class.
	 * @return the corresponding getter method or <code>null</code> if a
	 *         getter does not exist or in any case of access problem.
	 */
	public static Method getSetter(Class<?> c, String propertyName){
		Method setterMethod = getPrefixedMethod(c, propertyName, AccessorType.SETTER);
		return setterMethod;
	}
	
	private static Method getPrefixedMethod(Class<?> c, String propertyName, AccessorType accessorType){
		StringBuffer sb = new StringBuffer(propertyName);
		sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
		
		Class<?> type = Fields.getFieldType(c, propertyName);
		if(type == null){
			throw new IllegalArgumentException("Cannot access property '"+propertyName+"'");
		}

		String[] prefixes = null;
		Class<?>[] paramTypes = null;
		
		switch (accessorType) {
		case GETTER:
			if(Boolean.class.equals(type)){
				prefixes = new String[]{"is", "has"};
			}
			else {
				prefixes = new String[]{"get"};
			}
			break;
		case SETTER:
			paramTypes = new Class[]{type};
			prefixes = new String[]{"set"};
			break;

		default:
			break;
		}
		
		
		for (String prefix : prefixes) {
			StringBuffer sb2 = new StringBuffer(sb.toString());
			sb2.insert(0, prefix);
			Method accessorMethod = null;
			 
			try {
				accessorMethod = c.getMethod(sb2.toString(),paramTypes);
				return accessorMethod;
			} catch (Exception ignored) {
				ignored.printStackTrace();
			}
		}
		
		
		return null;		
	}
	
	/**
	 * Gets all the setter method available in class c (including methods
	 * defined in super types).
	 * 
	 * @param c
	 *            a <code>Class</code>.
	 * @return the methods defined in class <code>c</code> which names start
	 *         with <code>prefix</code>.
	 */
	public static List<Method> getMethodsWithPrefix(Class<?> c, String prefix) {
		Method[] allMethods = c.getMethods();
		List<Method> filteredMethods = new ArrayList<Method>();

		for (int i = 0; i < allMethods.length; i++) {
			if (allMethods[i].getName().startsWith(prefix)) {
				filteredMethods.add(allMethods[i]);
			}
		}

		return filteredMethods;
	}

	public static Object evaluateByPath(Object obj, String methodPath)
			throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		int index = methodPath.indexOf('.');
		Class<?> c = obj.getClass();

		if (index < 0) {
			Method m = c.getMethod(methodPath, (Class[])null);
			return m.invoke(obj, (Object[])null);
		} else {
			String sub1 = methodPath.substring(0, index);
			Method m = c.getMethod(sub1, (Class[])null);
			Object o = m.invoke(obj, (Object[])null);
			String sub2 = methodPath.substring(index + 1);
			return evaluateByPath(o, sub2);
		}
	}

}